/*
 * Package il.ac.biu.cs.grossmm.impl.server
 * File Node.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package il.ac.biu.cs.grossmm.impl.server;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Represents a node in a directed graph of resources. Nodes model a resource
 * such a as a component or a service. When one resource (represented by node
 * n1) is used by another resource (represented by node n2), then there's an
 * edge which which leaves n2 and enters n1. This edge is represented by
 * <tt>Asssociation</tt> object.
 * 
 */
public class Node<T extends Node<T>> {
    /**
     * associations with nodes used by this node, indexed by hint
     */
    private Map<Object, Association<T>> used = new HashMap<Object, Association<T>>();

    /**
     * associations with nodes which use this node, indexed by node
     */
    private Map<T, Association<T>> users = new HashMap<T, Association<T>>();

    /**
     * Reference to the top-level subclass of this object
     */
    final T _this;

    @SuppressWarnings("unchecked")
    Node() {
        _this = (T) this;
    }

    /**
     * Given a hint, gets a "from" association with this hint
     * 
     * @param hint
     *            the hint, used as a key to get an association
     * @return the "from" association with this hint or null if there is no such
     *         association
     */
    private Association<T> getAssociationByHint(Object hint) {
        return used.get(hint);
    }

    /**
     * Given a node, gets a "from" association which enters this node
     * 
     * @param node
     *            the node
     * @return the association which enters this node
     */
    private Association<T> getAssociationByNode(T node) {
        return node.users.get(_this);
    }

    /**
     * Gets a "used" node, associated with this node by the given hint
     * 
     * @param hint
     *            the hint
     * @return "used" node associated with this node by the given hint or null
     *         if there is no such node
     */
    protected T getByHint(Object hint) {
        // get an association by this hint
        Association<T> a = getAssociationByHint(hint);

        // return null if there is no association
        if (a == null)
            return null;

        // return the "used" node
        return a.used;
    }

    /**
     * Add a hint to a set of hints in association between <tt>this</tt> node
     * and node <tt>t</tt> (used by <tt>this</tt> node)
     * 
     * @param t
     *            the "used" (dependent) node
     * @param hint
     *            the hint
     * @throws Exception
     */
    protected void setHint(T t, Object hint) throws Exception {
        // get association between this node and node t
        Association<T> a = getAssociationByNode(t);

        // no association yet?
        if (a == null) {
            // noone uses t?
            if (t.users.size() == 0)
                t.used(); // node t is in use now, notify it about this event

            // create association using the hint
            a = new Association<T>(_this, t, hint);
            
            // add this node to the set of used nodes of node t
            t.users.put(_this, a);
        } else
            // add the hint to existing association
            a.add(hint);

        // add entry indexed by hint to the map of associations 
        used.put(hint, a);
    }

    protected boolean removeHint(Object hint) {
        Association<T> a = getAssociationByHint(hint);

        if (a == null)
            return false;

        used.remove(hint);

        if (a.remove(hint)) {
            if (a.size() == 0) {
                a.used.unusedBy(_this, null);
            }
            return true;
        } else
            return false;
    }

    /**
     * Sends an user event to all nodes which use this node
     * 
     * @param event
     * @param mustUnderstand
     *            true if users of this node who do not understand the event
     *            must stop using this node
     */
    protected void fireEvent(Object event, boolean mustUnderstand) {
        for (T t : users.keySet()) {
            t.onEvent(_this, users.get(t), event, mustUnderstand);
        }
    }

    /**
     * Invoked on user event from one of nodes used by this node
     * 
     * @param used
     *            node on which event has occured
     * @param hints
     *            hints by which node is associated with this node
     * @param event
     *            the event occured
     * @param mustUnderstand
     *            if true, in the case event is not understood this node should
     *            stop using the node which generated the event
     * 
     */
    protected void onEvent(T used, Set<Object> hints, Object event,
            boolean mustUnderstand) {
    }

    /**
     * Discard this node and all associations
     * 
     * @param e
     */
    protected void discard(Exception e) {
        // for each user of this resource
        for (Node<T> t : users.keySet()) {
            // get set of hints by which this user knows this resource
            Set<Object> hints = users.get(t);
            
//          remove all user's hints  
            for( Object hint : hints) {
                t.used.remove(hint);
            }
            
            // notify about resource discared
            t.discarded(_this, hints, e);
        }

        // for all resources used by this resource
        for (Association<T> a : used.values()) {
            // notify resource that is not longer in use by this resource
            a.used.unusedBy(_this, e);
        }

        // noone can use this resource
        users.clear();
        
        // this resource does not longer use any other resource
        used.clear();
    }

    /**
     * This node is no longer in use by another node
     * 
     * @param node
     *            the node which is no longer in use
     * @param e
     *            exception which describes the reason
     */
    protected void unusedBy(T user, Exception e) {
        users.remove(user);
        
        if (users.isEmpty())
            unused();
    }

    /**
     * Called when one of nodes (directly) used by this node has been discarded
     * 
     * @param used
     *            the node which has been discarded
     * @param hints
     *            hints by which node is associated with this node
     * @param e
     *            exception which discribes the reason for the discard
     */
    protected void discarded(T used, Set<Object> hints, Exception e) {
    }

    /**
     * Called when the node becomes used by at least one other node
     * 
     * @throws Exception
     * 
     */
    protected void used() throws Exception {
    }

    /**
     * Called when node is no longer in use by any other node. The base method
     * should be called at the end of overriden method.
     */
    protected void unused() {
        for (Association<T> a : used.values()) {
            a.used.unusedBy(_this, null);
        }

        used.clear();
    }
}
